proc compile_json {spec data} {
# data is plain old tcl values
# spec is defined as follows:
# {string} - data is simply a string, "quote" it if it's not a number
# {list} - data is a tcl list of strings, convert to JSON arrays
# {list list} - data is a tcl list of lists
# {list dict} - data is a tcl list of dicts
# {dict} - data is a tcl dict of strings
# {dict xx list} - data is a tcl dict where the value of key xx is a tcl list
# {dict * list} - data is a tcl dict of lists
# etc..
  while [llength $spec] {
    set type [lindex $spec 0]
    set spec [lrange $spec 1 end]
    
    switch -- $type {
      dict {
        lappend spec * string
        
        set json {}
        foreach {key val} $data {
          foreach {keymatch valtype} $spec {
            if {[string match $keymatch $key]} {
              lappend json [subst {"$key":[compile_json $valtype $val]}]
              break
            }
          }
        }
        return "{[join $json ,]}"
      }
      list {
        if {![llength $spec]} {
          set spec string
        } else {
          set spec [lindex $spec 0]
        }
        set json {}
        foreach {val} $data {
          lappend json [compile_json $spec $val]
        }
        return "\[[join $json ,]\]"
      }
      string {
        if {[string is double -strict $data]} {
          return $data
        } else {
          return "\"$data\""
        }
      }
      default {error "Invalid type"}
    }
  }
}

###   # Usage:
###   % compile_json {dict * list} {a {1 2 3} b {4 5}}
###   {"a":[1,2,3],"b":[4,5]}
###   
###   # Data may be nested:
###   % compile_json {dict * {list {dict d list}}} {a {{c 1} {d {2 2 2} e 3}} b {{f 4 g 5}}}
###   {"a":[{"c":1},{"d":[2,2,2],"e":3}],"b":[{"f":4,"g":5}]}
###   
###   For the last example, the specification reads as:
###   
###   {
###     dict <---------------- a dict
###       * {list     <------- where all (*) values are lists
###           {dict     <----- of dicts
###             d list  <----- where the value of "d" is a list
###           }                and all other values are strings (default)
###         }
###   }


proc mysql_pack {sql_qur db_name} {
    global info user_id user_pwd group_id group_pwd DB_NAME DB_PRV_NAME 
    set dict_mysql {"type" "sql_qur"  }
    dict append dict_mysql "db_name" "$db_name"
    dict append dict_mysql "params" "$sql_qur"
    #dict append dict_mysql "params" "[handle_string_special $sql_qur]"
    if {$db_name == $DB_NAME} {
        dict append dict_mysql "user" "$group_id"
        dict append dict_mysql "pass" "$group_pwd"
    } elseif {$db_name ==$DB_PRV_NAME} {
        dict append dict_mysql "user" "$user_id"
        dict append dict_mysql "pass" "$user_pwd"
    } else {
        return [-1,["db name $db_name is wrong"]]
    }
    dict append dict_mysql "para1" "for_future_use"
    dict append dict_mysql "para2" "for_future_use"
    set sky_response [query_sky $dict_mysql]

        if {[lindex $sky_response 0] == "HTTP/1.1 200 OK"} {
             log_info $info "pub: good! 200OK, resp code is [lindex $sky_response 0]" 2
             log_info $info "pub: good! 200OK, resp content (first 100 byte) is: [string range [lindex $sky_response 1] 0 99]" 2
        } else {
             log_info $info "pub: bad!, return code is [lindex $sky_response 0]" 2
             set err_msg [lindex $sky_response 1]
             log_info $info "resp is $err_msg" 2
             return [list -1,[list "$err_msg"]]
        }
    return [::json::json2dict [lindex $sky_response 1]]
}
proc query_sky {dict_name} {
    global info SERVER_URL
    set query_str [compile_json {dict} $dict_name]
    log_info $info "In query_sky, query str is : $query_str" 2
    set httpresp [httppost $SERVER_URL $query_str]
    return $httpresp
}

proc httppost {url json_str} {
    global info token PROXY_SERVER PROXY_PORT
    log_info $info "in httppost, url is $url" 2
    log_info $info "in httppost, json_str is $json_str" 2
    ::http::config -proxyhost $PROXY_SERVER -proxyport $PROXY_PORT -useragent httppost
    #::http::config -proxyhost hyin.pythonanywhere.com -proxyport 80 -useragent httppost

    #set json_str [string map [list \\ \\\\ \" \\" \n \\n / \\/ \b \\b \r \\r \t \\t] $json_str]
    set done 0

    set token [::http::geturl $url -query $json_str -keepalive 1]
    log_info $info "In httppost, finish query" 2
    set code [::http::code $token]
    set body [::http::data $token]
    log_info $info "In httppost, code is $code" 2
#    log_info $info "In httppost, body is $body" 2
    set return_list {}
    lappend return_list $code
    lappend return_list $body
    return $return_list
}
proc initial_user_checking {} {
    global info USERFILE SCRIPT_DIR
    #Initialization checking. Check file cfg/user_info.txt existence. and, if user info is enough. return a list with member ["1" username password], otherwise return a list with member ["0"]
    set filename $SCRIPT_DIR
    append filename "/"
    append filename $USERFILE
    set return_list []
    if [catch {set cfile [open $filename r]} err_info] {
       log_info $info "open user info file error: $err_info" 2
       log_info $info "filename is :$filename" 2
       lappend return_list "0"
    } else {
       close $cfile
       foreach entry [cf_read $filename] {
           lassign $entry name value
           switch $name {
               user_id { set user_id $value}
               user_pwd { set user_pwd $value}
               group_id { set group_id $value}
               group_pwd { set group_pwd $value}
           }
           log_info $info "$name ::: $value" 2
           }
       if {[info exists user_id] && [info exists user_pwd] && \
           [info exists group_id] && [info exists group_pwd]} {
           log_info $info "good,we got user and passwod" 2
           log_info $info "username is $user_id, group_id is $group_id" 2
           lappend return_list "1"
           lappend return_list $user_id
           lappend return_list $user_pwd
           lappend return_list $group_id
           lappend return_list $group_pwd
       } else {
           log_info $info "bad, the user_info is not enough! Please check file $filename" 2
           log_info $info "username is $user_id, group_id is $group_id" 2
           lappend return_list "0"
       }
    } 
    return $return_list
}
proc user_profile_setting {} {
#this proc was used to create/manage user profile, save to file cfg/user_info.txt
        global info user_id user_pwd user_raw_pwd group_id group_pwd group_raw_pwd commit_user_profile user_email_adr user_company
        set user_profile .user_profile
        catch {destroy $user_profile}
        toplevel $user_profile
        wm title $user_profile "Create/modify user profile"
        wm iconname $user_profile "iconname?"
        positionWindow $user_profile

        frame $user_profile.must
        frame $user_profile.option
        frame $user_profile.must.left
        frame $user_profile.must.right
        frame $user_profile.option.left
        frame $user_profile.option.right
        frame $user_profile.command
        pack $user_profile.must $user_profile.option $user_profile.command -side top -expand yes -padx 10 -pady 10 -fill both
        pack $user_profile.must.left $user_profile.must.right -side left -expand yes -padx 10 -pady 10 -fill both
        pack $user_profile.option.left $user_profile.option.right -side left -expand yes -padx 10 -pady 10 -fill both

        label $user_profile.must.left.user_id -text "User Name" -padx 0 -width 15 -anchor e
        label $user_profile.must.left.user_pwd -text "User Password" -padx 0 -width 15 -anchor e
        label $user_profile.must.left.group_id -text "Group name" -padx 0 -width 15 -anchor e
        label $user_profile.must.left.group_pwd -text "Group Password" -padx 0 -width 15 -anchor e
        entry $user_profile.must.right.user_id -width 20 -relief sunken -textvariable user_id
        entry $user_profile.must.right.user_pwd -validate key -show "*" -vcmd {expr {[string length %P]<=16}} -width 20 -relief sunken -textvariable user_raw_pwd
        entry $user_profile.must.right.group_id -width 20 -relief sunken -textvariable group_id
        entry $user_profile.must.right.group_pwd -validate key -show "*" -vcmd {expr {[string length %P]<=16}} -width 20 -relief sunken -textvariable group_raw_pwd

        pack $user_profile.must.left.user_id $user_profile.must.left.user_pwd $user_profile.must.left.group_id $user_profile.must.left.group_pwd -side top -expand yes -pady 2 -anchor w
        pack $user_profile.must.right.user_id $user_profile.must.right.user_pwd $user_profile.must.right.group_id $user_profile.must.right.group_pwd -side top -expand yes -pady 2 -anchor w

        label $user_profile.option.left.email_adr -text "Email Address" -padx 0 -width 15 -anchor e
        label $user_profile.option.left.company -text "Company" -padx 0 -width 15 -anchor e
        entry $user_profile.option.right.email_adr -width 20 -relief sunken -textvariable user_email_adr
        entry $user_profile.option.right.company -width 20 -relief sunken -textvariable user_company

        pack $user_profile.option.left.email_adr $user_profile.option.left.company -side top -expand yes -pady 2 -anchor w
        pack $user_profile.option.right.email_adr $user_profile.option.right.company -side top -expand yes -pady 2 -anchor w

        button $user_profile.command.register -text Register -command "register_user_file  "
        button $user_profile.command.confirm -text Confirm -command "update_user_file"
        button $user_profile.command.cancel -text Cancel -command "set commit_user_profile 0 "
        pack $user_profile.command.register $user_profile.command.confirm $user_profile.command.cancel -side left -expand 1
        vwait commit_user_profile
        catch {destroy $user_profile}
        .f.out_window.cur_title configure -text "        Current operation environment            user_id:$user_id    group_id:$group_id"
        return $commit_user_profile
}
proc register_user_file {} {
    global user_id user_pwd user_raw_pwd group_id group_pwd group_raw_pwd commit_user_profile user_email_adr user_company info
    if {$user_raw_pwd != "Original_User_Pwd_a2B3c4D5"} { set user_pwd [::md5::md5 -hex $user_raw_pwd]}
    if {$group_raw_pwd != "Original_Group_Pwd_a2B3c4D5"} {set group_pwd [::md5::md5 -hex $group_raw_pwd]}
    switch [dict get [check_user "user" $user_id $user_pwd] code] {
        0 { log_info $info "good! user name is not exist, going to create it. " 2}
       -1 {
            tk_messageBox -icon info -type ok -title "Fail" -message "Network error, please try again later. or contact network manager"
            return 0
          }
        1 {
            tk_messageBox -icon info -type ok -title "Fail" -message "User $user_id already exist, please choose new name"
            return 0 
          }
        2 {
            tk_messageBox -icon info -type ok -title "Fail" -message "User $user_id already exist and user password is not matching, 
please choose new user name"
            return 0
          }
    }

    set group_check [dict get [check_user "group" $group_id $group_pwd] code]
    
    switch $group_check {
        0 { log_info $info "good! group name is not exist, going to create it. " 2}
       -1 {
            tk_messageBox -icon info -type ok -title "Fail" -message "Network error, please try again later. or contact network manager"
            return 0
          }
        1 {
            tk_messageBox -icon info -type ok -title "Fail" -message "User $group_id already exist and group password is correct, going to use it"
          }
        2 {
            tk_messageBox -icon info -type ok -title "Fail" -message "User $group_id already exist and group password is not matching, 
please choose new group name"
            return 0
          }
    }
    #register group profile
    if {$group_check == 0} {
        set register_group_response [register_group_profile $group_id $group_pwd $user_id]
        set register_group_code [dict get $register_group_response code]
        set register_group_msg [dict get $register_group_response msg]
        if {$register_group_code == 1} {
            log_info $info "Succeeded to register group profile. group_id: $user_id, owner_id: $user_id" 2
        } else {
            log_info $info "Failed to input group profile. group_id: $group_id, owner_id: $user_id" 2
        }
        log_info $info "return message: $register_group_msg" 2
    }

    #register user profile
    set register_user_response [register_user_profile $user_id $user_pwd $group_id $user_email_adr $user_company]
    set register_user_code [dict get $register_user_response code]
    set register_user_msg [dict get $register_user_response msg]
    if {$register_user_code == 1} {
        log_info $info "Succeeded to register user profile. user_id: $user_id, group_id: $group_id" 2
        log_info $info "return message: $register_user_msg" 2
        set commit_user_profile 1
        return 1
    } else {
        log_info $info "Failed to input user profile. user_id: $user_id, group_id: $group_id" 2
        log_info $info "return message: $register_user_msg" 2
        return 0
    }
}

proc register_group_profile {group_id group_pwd owner_id} {
    global info
    #register new user
    #if group exist, overwrite it
    #if group not exist, create it
    #return 0 means action succeeded
    #return 1 means action failed
    set dict_usr_ctl {"type" "usr_ctl"}
    dict append dict_usr_ctl "action" "update"
    dict append dict_usr_ctl "sub_type" "group"
    dict append dict_usr_ctl "id" "$group_id"
    dict append dict_usr_ctl "pwd" "$group_pwd"
    dict append dict_usr_ctl "owner" "$owner_id"
    set sky_response [query_sky $dict_usr_ctl]

    if {[string first "200 OK" [lindex $sky_response 0]] != -1} {
         log_info $info "pub: good! 200OK, resp code is [lindex $sky_response 0]" 2
         log_info $info "pub: good! 200OK, resp content (first 100 byte) is: [string range [lindex $sky_response 1] 0 99]" 2
    } else {
         log_info $info "pub: bad!, return code is [lindex $sky_response 0]" 2
         log_info $info "resp is [lindex $sky_response 1]" 2
         return -1
    }
    return [::json::json2dict [lindex $sky_response 1]]
}

proc register_user_profile {user_id user_pwd group_id user_email_adr user_company} {
    global info
    #register new user
    #if user/group exist, overwrite it
    #if user/group not exist, create it
    #return 0 means action succeeded
    #return 1 means action failed
    set dict_usr_ctl {"type" "usr_ctl"}
    dict append dict_usr_ctl "action" "update"
    dict append dict_usr_ctl "sub_type" "user"
    dict append dict_usr_ctl "id" "$user_id"
    dict append dict_usr_ctl "pwd" "$user_pwd"
    dict append dict_usr_ctl "group" "$group_id"
    dict append dict_usr_ctl "email" "$user_email_adr"
    dict append dict_usr_ctl "company" "$user_company"
    set sky_response [query_sky $dict_usr_ctl]

    if {[string first "200 OK" [lindex $sky_response 0]] != -1} {
         log_info $info "pub: good! 200OK, resp code is [lindex $sky_response 0]" 2
         log_info $info "pub: good! 200OK, resp content (first 100 byte) is: [string range [lindex $sky_response 1] 0 99]" 2
    } else {
         log_info $info "pub: bad!, return code is [lindex $sky_response 0]" 2
         log_info $info "resp is [lindex $sky_response 1]" 2
         return -1
    }
    return [::json::json2dict [lindex $sky_response 1]]
} 
proc check_user {sub_type user_id user_pwd} {
    global info 
    #Check if user_id is already exist in database
    #return 0 means user not exist
    #return 1 means user exist, usable, and user_pwd is good
    #return 2 means user exist but not usable, probably user_pwd is bad
    set dict_usr_ctl {"type" "usr_ctl"}
    dict append dict_usr_ctl "action" "check"
    dict append dict_usr_ctl "sub_type" "$sub_type"
    dict append dict_usr_ctl "id" "$user_id"
    dict append dict_usr_ctl "pwd" "$user_pwd"
    dict append dict_usr_ctl "email" "ignore"
    dict append dict_usr_ctl "company" "ignore"

    set sky_response [query_sky $dict_usr_ctl]
    
    if {[string first "200 OK" [lindex $sky_response 0]] != -1} {
         log_info $info "pub: good! 200OK, resp code is [lindex $sky_response 0]" 2
         log_info $info "pub: good! 200OK, resp content (first 100 byte) is: [string range [lindex $sky_response 1] 0 99]" 2
    } else {
         log_info $info "pub: bad!, return code is [lindex $sky_response 0]" 2
         log_info $info "resp is [lindex $sky_response 1]" 2
         return -1
    }
    return [::json::json2dict [lindex $sky_response 1]]
}
proc check_group {group_id group_pwd} {
}
proc input_user_profile {user_id user_pwd group_id group_pwd} {
}
proc update_user_file {} {
    global user_id user_pwd user_raw_pwd group_raw_pwd group_id group_pwd info DB_NAME DB_PRV_NAME SCRIPT_DIR USERFILE commit_user_profile

    if {$user_raw_pwd != "Original_User_Pwd_a2B3c4D5"} { set user_pwd [::md5::md5 -hex $user_raw_pwd]}
    if {$group_raw_pwd != "Original_Group_Pwd_a2B3c4D5"} {set group_pwd [::md5::md5 -hex $group_raw_pwd]}

    set filename $SCRIPT_DIR
    append filename "/"
    append filename $USERFILE

    cf_write $filename user_id user_pwd group_id group_pwd
    log_info $info "update user profile. user_id: $user_id, group_id: $group_id" 2
    set commit_user_profile 1
}
proc cf_write {fname args} {
  set fd [open $fname w]
  chan config $fd -encoding utf-8
  set data [list]
  foreach varName $args {
    upvar 1 $varName var
    lappend data [list $varName $var]
  }
  puts $fd $data
  close $fd
}

proc cf_read fname {
  set fd [open $fname]
  chan config $fd -encoding utf-8
  set data [read $fd]
  close $fd
  set data
}

#set cfile [file join [file dir [info script]] conf.txt]
#cf_write $cfile a b c
#foreach entry [cf_read $cfile] {
#  lassign $entry name value
#  puts "$name: $value"
#}
